package upnp

import (
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
)

// FROM: http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf
// Service description section, only include REQUIRED fields
type srvResponse struct {
	// configuration number to which the device description belongs
	ConfigID string `xml:"configId,attr"`
	// version templates
	SpecVersion specVersionType `xml:"specVersion"`
	// action list
	ActionList []actionType `xml:"actionList>action"`
	// state table
	StatesTable []stateType `xml:"serviceStateTable>stateVariable"`
}

type actionType struct {
	// action name
	Name string `xml:"name"`
	// agrument list
	Arguments []argumentType `xml:"argumentList>argument"`
}

type argumentType struct {
	// argument name
	Name string `xml:"name"`
	// must be in or out
	Direction string `xml:"direction"`
	// TODO: comment
	RelatedStateVariable string `xml:"relatedStateVariable"`
}

type stateType struct {
	// state name
	Name string `xml:"name"`
	// state type
	Type string `xml:"dataType"`
}

// Service service
type Service struct {
	ID string
	// scpd url in full
	scpd string
	// control url in full
	ctl string
	// event url in full
	event string
	// service type
	stype string
	// action list
	actions []Action

	parsed bool
}

func (d *Device) loadService(dev deviceType) error {
	for _, s := range dev.ServiceList {
		s.SCPDURL = strings.TrimPrefix(s.SCPDURL, "/")
		s.ControlURL = strings.TrimPrefix(s.ControlURL, "/")
		s.EventSubURL = strings.TrimPrefix(s.EventSubURL, "/")
		d.serviceList = append(d.serviceList, &Service{
			ID:    s.ServiceID,
			scpd:  d.urlPrefix + "/" + s.SCPDURL,
			ctl:   d.urlPrefix + "/" + s.ControlURL,
			event: d.urlPrefix + "/" + s.EventSubURL,
			stype: s.ServiceType,
		})
	}
	if len(dev.DeviceList) > 0 {
		for _, sub := range dev.DeviceList {
			if err := d.loadService(sub); err != nil {
				return err
			}
		}
	}
	return nil
}

func getActArgs(action actionType, key string) []string {
	var ret []string
	for _, arg := range action.Arguments {
		if arg.Direction == key {
			ret = append(ret, arg.Name)
		}
	}
	return ret
}

func (s *Service) parse() error {
	resp, err := http.Get(s.scpd)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		data, _ := ioutil.ReadAll(resp.Body)
		return fmt.Errorf("GET scpd info fail, http_code=%d\n%s", resp.StatusCode, string(data))
	}
	var srv srvResponse
	if err := xml.NewDecoder(resp.Body).Decode(&srv); err != nil {
		return err
	}
	for _, act := range srv.ActionList {
		switch act.Name {
		case "GetGenericPortMappingEntry":
			s.actions = append(s.actions,
				newGetPortMappingAction(s, getActArgs(act, "in"), getActArgs(act, "out")))
		case "AddPortMapping":
			s.actions = append(s.actions,
				newAddPortMappingAction(s, getActArgs(act, "in"), getActArgs(act, "out")))
		case "DeletePortMapping":
			s.actions = append(s.actions,
				newDeletePortMappingAction(s, getActArgs(act, "in"), getActArgs(act, "out")))
		case "GetExternalIPAddress":
			s.actions = append(s.actions,
				newGetExtIPAction(s, getActArgs(act, "in"), getActArgs(act, "out")))
		}
	}
	// data, _ := json.MarshalIndent(srv, "", "    ")
	// fmt.Println(string(data))
	s.parsed = true
	return nil
}
